using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Forms;
using SKYPE4COMLib;
using System.Speech.Synthesis;
using System.Speech.AudioFormat;
using System.Collections.Specialized;

namespace Ajc {
    public class Skyping {
        [System.Runtime.InteropServices.DllImport("winmm.DLL", EntryPoint = "PlaySound", SetLastError = true)]
        private static extern bool PlaySound(string szSound, System.IntPtr hMod, PlaySoundFlags flags);

        [System.Flags]
        public enum PlaySoundFlags : int {
            SND_SYNC = 0x0000,
            SND_ASYNC = 0x0001,
            SND_NODEFAULT = 0x0002,
            SND_LOOP = 0x0008,
            SND_NOSTOP = 0x0010,
            SND_NOWAIT = 0x00002000,
            SND_FILENAME = 0x00020000,
            SND_RESOURCE = 0x00040004
        }
        /// <summary>
        /// We need min. this version of the Skype protocol
        /// </summary>
        private const int SKYPE_PROTOCOL_VERSION = 5;

        private const string ANNOUNCE_FOLDER = "announce";

        private Skype oSkype = new Skype();
        private Conversion oConvert = new Conversion();
        private Timer skypeAuthTimer = new Timer();
        private Logging log;
        private UserInterface userInterface;

        /// <summary>
        /// Internal info state 
        /// </summary>
        private bool isAttachedToSkype = false;
        /// <summary>
        /// do we wait for the authentification from skype?
        /// </summary>
        private bool awaitingAuthentification = false;

        // you can change the following variables without any sideeffects
        // so we don't need get/set method for those
        public bool AcceptIncomingCalls = true;
        public bool JoinIncomingCalls = true;
        public bool AnnounceNewCaller = true;
        /// <summary>
        /// We wait this time in s. before we accept or join the call. 
        /// </summary>
        public int WaitingTimeBeforeAcceptCall = 3;

        
        /// <summary>
        /// 'null' is valid value here and allows all users
        /// </summary>
        public StringCollection AllowedUsers = null;

        private bool startSkypeIfNeeded = false;
        private bool silenMode = false;

        public enum AttachStatus {
            ATTACHED, NOT_ATTACHED
        }
        public delegate void AttachStatusChanged(AttachStatus status);
        public event AttachStatusChanged OnAttachStatusChanged;

        public delegate void SilentModeChanged(bool silentMode);
        public event SilentModeChanged OnSilentModeChanged;

        public Skyping(UserInterface userInterface, Logging loggingClass) {
            this.userInterface = userInterface;
            this.log = loggingClass;

            this.skypeAuthTimer.Tick += new EventHandler(skypeAuthTimerEvent);
        }

        /// <summary>
        /// Starts the attempt to attach to Sykpe
        /// </summary>
        public void attachToSkype() {
            if (!this.oSkype.Client.IsRunning) {
                if (this.startSkypeIfNeeded) {
                    this.log.info("Skype is not running. Starting Skype...");
                    this.oSkype.Client.Start(true, true);
                } else {
                    this.log.info("Skype is not running. This tool connects to Skype automatically when Skype starts up.");
                }                
            }
            this.oSkype.CallStatus += this.Skype_CallStatus;
            ((_ISkypeEvents_Event)this.oSkype).AttachmentStatus += attachmentStatusEvent;
            this.oSkype.Attach(SKYPE_PROTOCOL_VERSION, false);
        }

        /// <summary>
        /// Shutdowns the current Skype application.
        /// </summary>
        public void shutdownSkype() {
            this.log.info("Shutting down skype");
            if (this.oSkype.Client.IsRunning) {
                this.oSkype.Client.Shutdown();
            }
        }

        /// <summary>
        /// Our method for the "AttachmentStatus" event. 
        /// </summary>
        /// <param name="status"></param>
        private void attachmentStatusEvent(TAttachmentStatus status) {
            this.log.debug("Skype attach status was " + this.oConvert.AttachmentStatusToText(status));

            if (status == TAttachmentStatus.apiAttachSuccess) {
                this.log.info("Attached successfully to Skype");                
                this.isAttachedToSkype = true;
                // we don't need to notice the user anymore
                this.awaitingAuthentification = false;
                this.oSkype.SilentMode = this.silenMode;
                this.oSkype.SilentModeStatusChanged += new _ISkypeEvents_SilentModeStatusChangedEventHandler(oSkype_SilentModeStatusChanged);
                OnAttachStatusChanged(AttachStatus.ATTACHED);
            } else if (status == TAttachmentStatus.apiAttachNotAvailable) {
                // skype was shutdown
                this.isAttachedToSkype = false;
                this.awaitingAuthentification = false;
                OnAttachStatusChanged(AttachStatus.NOT_ATTACHED);
            } else if (status == TAttachmentStatus.apiAttachAvailable) {
                // skype was started, so try to attach again
                this.oSkype.Attach(SKYPE_PROTOCOL_VERSION, false);
            } else if (status == TAttachmentStatus.apiAttachPendingAuthorization) {
                this.awaitingAuthentification = true;
                // start the timer to notice the user about the program authentification
                this.skypeAuthTimer.Interval = 2000;
                this.skypeAuthTimer.Start();
            } else if (status == TAttachmentStatus.apiAttachRefused) {
                // we don't need to notice the user anymore
                this.awaitingAuthentification = false;
                string msg = "Skype refuses the attach attempt. You must .... (TODO: descripe the way in the skype options) and restart this tool (TODO: change this)";
                this.log.error(msg);
                this.userInterface.showMessageBoxModal(msg);
            }
        }

        void oSkype_SilentModeStatusChanged(bool Silent)
        {
            OnSilentModeChanged(Silent);
        }

        private void skypeAuthTimerEvent(object source, System.EventArgs e) {
            this.log.debug("skypeAuthTimerEvent raised");
            this.skypeAuthTimer.Stop();

            if (this.awaitingAuthentification && ! this.isAttachedToSkype) {
                string msg = "You have to allow access to skype for this program. Plesae have a look at Skype.";
                this.log.info(msg);
                //this.userInterface.showMessageBoxModal(msg);
                // 1 minute
                this.skypeAuthTimer.Interval = 60000;
                this.skypeAuthTimer.Start();
                this.log.debug("skypeAuthTimerEvent started again with interval: " + this.skypeAuthTimer.Interval);
            }
        }

        /// <summary>
        /// Find an active call
        /// </summary>
        /// <returns>the call object or null if the function fails</returns>
        private Call findActiveCall() {
            foreach (Call call in oSkype.ActiveCalls) {
                if (call.Status == TCallStatus.clsInProgress) {
                    return call;
                }
            }

            return null;
        }

        private void Skype_CallStatus(Call call, TCallStatus status) {
            if (!this.AcceptIncomingCalls && !this.JoinIncomingCalls) {
                return;
            }
            this.log.debug(">Call " + call.Id + " status: " + oConvert.CallStatusToText(status));
            if (status == TCallStatus.clsRinging && (call.Type == TCallType.cltIncomingP2P || call.Type == TCallType.cltIncomingPSTN)) {
                log.info("Incoming call from " + call.PartnerDisplayName);

                // check allowed users
                if (this.AllowedUsers != null) {
                    bool isAllowed = false;
                    string caller = call.PartnerHandle;
                    foreach (string user in this.AllowedUsers) {
                        if (caller.Equals(user)) {
                            // found
                            isAllowed = true;
                            break;
                        }
                    }
                    if (!isAllowed) {
                        log.info("The caller is not in the allowedUsers list. Ignoring this call.");
                        return;
                    }
                }

                //log.info("Finding active conference/call ...");
                Call currentCall = findActiveCall();
                if (currentCall == null) {
                    if (this.AcceptIncomingCalls) {
                        log.info("No active Call found, skipping the join, but accepting the call");
                        log.debug("answer call...");
                        wait();
                        call.Answer();
                        announce(call);
                    } else {
                        log.info("No active Call found, skipping the join. You have to answer manually the call.");
                    }
                } else {
                    if (this.JoinIncomingCalls) {
                        log.info("Found active conference/call: " + currentCall.Id + ". Joining new call into the active one.");
                        log.debug("join to current call...");
                        wait();
                        currentCall.Join(call.Id);
                        log.debug("answer call...");
                        call.Answer();
                        announce(call);
                    } else {
                        log.info("Found active conference/call: " + currentCall.Id + ". Do NOT joining the call because 'joinIncomingCalls' is disabled.");
                    }
                }
                log.debug("done");
            }
        }

        private void wait()
        {
            if (this.WaitingTimeBeforeAcceptCall > 0)
            {
                System.Threading.Thread.Sleep(this.WaitingTimeBeforeAcceptCall * 1000);
            }
        }

        private void announce(Call call) {
            string sayThis = "Call accepted from " + call.PartnerDisplayName + "!";
            log.debug("Announce: " + sayThis);

            string announceWav = ANNOUNCE_FOLDER + @"\" + sayThis + ".wav";
            log.debug("announceWav: " + announceWav);

            // try to find an associated .wav
            if (!File.Exists(announceWav)) {
                log.debug("File doesn't exists");
                if (!Directory.Exists(ANNOUNCE_FOLDER)) {
                    log.debug("Directory doesn't exists, creating...");
                    Directory.CreateDirectory(ANNOUNCE_FOLDER);
                }
                log.debug("create speech wav");
                SpeechSynthesizer speech = new SpeechSynthesizer();
                SpeechAudioFormatInfo formatInfo = new SpeechAudioFormatInfo(System.Speech.AudioFormat.EncodingFormat.Pcm, 16000, 16, 1, 16000, 2, null);
                speech.SetOutputToWaveFile(announceWav, formatInfo);
                speech.Speak(sayThis);
            }
            log.debug("sleeping 2000ms");
            System.Threading.Thread.Sleep(2000);

            // play the .wav on skype
            log.debug("play sound...");
            PlaySound(Path.GetFullPath(announceWav), new System.IntPtr(), PlaySoundFlags.SND_ASYNC);
            //log.debug("set input device to file");
            //call.set_InputDevice(TCallIoDeviceType.callIoDeviceTypeFile, Path.GetFullPath(announceWav));
            //log.debug("set input device to soundcard");
            //call.set_InputDevice(TCallIoDeviceType.callIoDeviceTypeSoundcard, "default");
        }

        private String getEscapedSkypeName(String skypeName) {
            return System.Web.HttpUtility.UrlEncode(skypeName);
        }

        public bool StartSkypeIfNeeded {
            get {
                return this.startSkypeIfNeeded;
            }
            set {
                // This value could be set by the user long time after the start.
                this.startSkypeIfNeeded = value;

                if (!this.oSkype.Client.IsRunning) {
                    this.log.info("Starting Skype...");
                    this.oSkype.Client.Start(true, true);
                }
            }
        }

        public bool SilenMode
        {
            get
            {
                return this.silenMode;
            }
            set
            {
                this.silenMode = value;
                if (isAttachedToSkype)
                {
                    this.oSkype.SilentMode = value;
                }
            }
        }

        public List<SkypeContact> getContacts()
        {
            List<SkypeContact> contactList = new List<SkypeContact>();
            if (isAttachedToSkype)
            {

                foreach (User user in this.oSkype.Friends)
                {
                    string identifier = user.DisplayName;
                    if (identifier == null || identifier.Trim().Length == 0)
                    {
                        identifier = user.FullName;
                    }
                    if (identifier == null || identifier.Trim().Length == 0)
                    {
                        identifier = user.Handle;
                    }

                    contactList.Add(new SkypeContact(user.Handle, identifier));
                }
            }
            return contactList;
        }
    }

    public class SkypeContact
    {
        private String handle;
        private String display;

        public SkypeContact(String handle, String display)
        {
            this.handle = handle;
            this.display = display;
        }

        public String Handle
        {
            get { return this.handle; }
        }
        public String Display
        {
            get { return this.display; }
        }


        public override String ToString()
        {
            return this.display + " (" +this.handle +")";
        }
    }
}

